package com.lohika  
{
	import com.lohika.builder.*;
	import com.lohika.util.GameUtil;
	
	import events.*;
	
	import flash.events.*;
	import flash.utils.*;
	

	/**
	 * Model 
	 * 
	 * @author Vitaly Vrublevskyy
	 */
	public class Grid extends EventDispatcher
	{
		private var director:Director; 
		private var crystalBuilder:AbstractCrystalBuilder;
		
		private var _source:Array;
		public function get source():Array 
		{
			return _source;
		}
		
		public function set source(value:Array):void 
		{
			_source = value;
		}
		
		public function Grid()
		{
			director = new Director();
			crystalBuilder = new CrystalBuilder();
			
			director.setBuilder(crystalBuilder);			
		}
		
		public function initialize():void
		{
			do
			{
				randomizedFill();
			} 
			while ( !posibleMove() )
		}
		
		/**
		 * Generate 2x2 table without any "match 3 lines"
		 * 
		 */
		private function randomizedFill():void 
		{
			_source  = []; //reset
			for (var column:int = 0; column < Constant.COLUMNS; column++) 
			{
				_source[column] = [];
				for (var row:int = 0; row < Constant.ROWS; row++) 
				{
					var neighbours:Vector.<uint> = getUpAndLeftNeighbours(column, row);
					//Regenerete if equal neighbour
					do 
					{
						var type:uint = int(Math.random() * Constant.ENTITY_KINDS + 1); 
					}
					while ( neighbours.indexOf(type) != -1 )
					
					add(column, row, type);
				}
			}
			
			/**
			 * Inner function
			 */
			function getUpAndLeftNeighbours(column:uint, row:uint):Vector.<uint>
			{
				var result:Vector.<uint> = new Vector.<uint>;
				if (column > 0)
					result.push( _source[column - 1][row].type )
				if (row > 0)
					result.push( _source[column][row - 1].type );

				return result;
			}
		}
		
		/**
		 * Build new entity  
		 */		
		private function add(column:int, row:int, type:uint):void
		{
			director.construct(column, row, type); //Builder DP
			
			_source[column][row] = director.entity;
		}
		
		public function existMatchesLines():Boolean
		{
			return findEqualLines(true).length > 0;
		}
		
		/**
		 * @force flag that only verify the existence one  m3 lines
		 * @return Array of matched lines in our game
		 * 
		 */		
		public function findEqualLines(force:Boolean = false):Array 
		{
			var result:Array = [];
			
			var equals:Array = [];
			//by vertical lines
			for ( var column:int = 0; column < Constant.COLUMNS; column++) 
			{
				for ( var row:int = 0; row < Constant.ROWS - 2; row++) 
				{
					equals = equalVertical(column, row);
					if ( equals.length > 2 )
					{
						result.push(equals);
						row += equals.length-1; //jump
						
						if (force)	return result; // exit
					}
				}
			}			
			
			//by horizontal lines
			for ( row = 0; row < Constant.ROWS; row++) 
			{
				for ( column = 0; column < Constant.COLUMNS - 2; column++) 
				{
					equals = equalHorizontal(column, row)
					if (equals.length > 2)
					{
						result.push(equals);
						column += equals.length-1; //jump
						
						if (force)	return result; // exit
					}
				}
			}
			
			return result;
		}


		public function removeMatchedLines():void
		{
			var equals : Array = findEqualLines();
			removeStepByStepMatchedLines(equals);
		}
		
		/**
		 * Replace match3 lines with new random generated values
		 * Recursively check result of prev running   
		 * @param equals
		 * 
		 */		
		public function removeStepByStepMatchedLines(equals:Array):void
		{
			if (equals.length == 0)
			{
				//TODO: improve cascade effect (when user click)
				if( posibleMove() )
					dispatchEvent( new GameEvent(GameEvent.REMOVED_ALL_LINES));
				else
					dispatchEvent( new GameEvent(GameEvent.GAME_OVER, 0, true));
				
				return; //Exit from recursia
			}
			
			var unique:Dictionary = new Dictionary(true); //for deleting items just one time
			for each (var matchedLine:Array in equals) 
			{
				dispatchEvent( new GameEvent(GameEvent.UPDATE_SCRORE, matchedLine.length) );
				for each (var entity:Entity in matchedLine) 
				{
					if ( !unique[entity] )
					{
						source[entity.column][entity.row] = null;
						affectAbove(entity);					
						dispatchEvent(new EntityEvent(EntityEvent.REMOVED, entity));
						
						unique[entity] = true;
					}
				}
			}
			unique = null;
		
			
			addNewEntities(); 
			
			var matches : Array = findEqualLines();
			
			// recursia with delay 1 sec
			setTimeout( removeStepByStepMatchedLines , 1000, matches );
		}
		
		private function addNewEntities():void
		{
			for ( var column:int = 0; column < Constant.COLUMNS; column++) 
			{
				for(var row:int = Constant.ROWS - 1; row >= 0; row--) 
				{
					if ( source[column][row] == null )
					{
						var type:uint = int(Math.random() * Constant.ENTITY_KINDS + 1); 
						add(column, row, type);
						// send notification to Board which is UI container for entity
						dispatchEvent(new EntityEvent(EntityEvent.ADDED, director.entity)); 
					}
				}
			}
		}
		
		
		private function affectAbove(entity:Entity):void
		{
			for(var row:int = entity.row-1; row >= 0; row--) 
			{
				var above:Entity = source[entity.column][row] as Entity
				if ( above )
				{
					above.row ++;
					source[entity.column][row + 1] = above;
					above.relocate(); //move to
					source[entity.column][row] = null;
				}
			}
			
		}		
		
		private function posibleMove():Boolean 
		{
			return GameUtil.isPosibleMove(source);
		}
		
		/**
		 * @return  Returns a Array of equals neighbor values that contains in current column
		 */
		
		private function equalHorizontal(column:uint, row:uint):Array
		{
			var result:Array = new Array( source[column][row] );
			for (var i:int = 1; column + i < Constant.COLUMNS; i++) 
			{
				if (  ( source[column][row] as Entity).type ==  (source[column+ i][row ] as Entity).type) 
				{
					result.push( source[column + i][row] )
				}
				else 
				{
					return result;
				}
			}
			
			return result;
		}
		
		
		
		private function equalVertical(column:uint, row:uint):Array
		{
			var result:Array = new Array( source[column][row] );
			for (var i:int = 1; row + i < Constant.ROWS; i++) 
			{
				if ( ( source[column][row] as Entity).type ==  (source[column][row + i] as Entity).type ) 
				{
					result.push( source[column][row + i] )
				}
				else 
				{
					return result;
				}
			}
			
			return result;
		}
	}
	


}